/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;

namespace DaveSexton.DocProject.Wizards.Foundation
{
	/// <remarks>
	/// <para>It's important to note that this class does not check for circular references, which will cause infinite loops when querying certain properties or calling certain methods.</para>
	/// <para>This class is not thread-safe.</para>
	/// 
	/// <para>Insertion vs. Injection vs. Splicing</para>
	/// <para>Given the following list to be inserted, injected or spliced: </para>
	/// <code>1 2 3 4 5</code>
	/// 
	/// <para>into the following list of steps: </para>
	/// <code>A B C D</code>
	/// 
	/// <para>inserting 3 after B or before C would produce the following lists: </para>
	/// <code>A B 3 C D
	/// 1 2
	/// 4 5</code>
	/// 
	/// <para>injecting 3 after B or before C would produce the following list: </para>
	/// <code>A B 1 2 3 4 5 C D</code>
	/// 
	/// <para>splicing 3 before C would produce the following lists: </para>
	/// <code>A B 1 2 3 C D
	/// 4 5</code>
	/// 
	/// <para>splicing 3 after B would produce the following lists: </para>
	/// <code>A B 3 4 5 C D
	/// 1 2</code>
	/// </remarks>
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
	public abstract class WizardStep : MarshalByRefObject, IDisposable, IEnumerable<WizardStep>
	{
		#region Public Properties
		public abstract string Name { get; }

		public abstract bool CanPreloadUserInterface { get; }

		public WizardStep PreviousStep
		{
			get
			{
				return previousStep;
			}
		}

		public WizardStep NextStep
		{
			get
			{
				return nextStep;
			}
		}

		public WizardStep FirstStep
		{
			get
			{
				WizardStep firstStep = this;

				while (firstStep.previousStep != null)
					firstStep = firstStep.previousStep;

				return firstStep;
			}
		}

		public WizardStep LastStep
		{
			get
			{
				WizardStep lastStep = this;

				while (lastStep.nextStep != null)
					lastStep = lastStep.nextStep;

				return lastStep;
			}
		}

		public WizardStep PreviousVisibleStep
		{
			get
			{
				WizardStep previousVisibleStep = previousStep;

				while (previousVisibleStep != null && !previousVisibleStep.Visible)
					previousVisibleStep = previousVisibleStep.previousStep;

				return previousVisibleStep;
			}
		}

		public WizardStep NextVisibleStep
		{
			get
			{
				WizardStep nextVisibleStep = nextStep;

				while (nextVisibleStep != null && !nextVisibleStep.Visible)
					nextVisibleStep = nextVisibleStep.nextStep;

				return nextVisibleStep;
			}
		}

		public WizardStep FirstVisibleStep
		{
			get
			{
				WizardStep firstVisibleStep = FirstStep;

				while (firstVisibleStep != null && !firstVisibleStep.Visible)
					firstVisibleStep = firstVisibleStep.nextStep;

				return firstVisibleStep;
			}
		}

		public WizardStep LastVisibleStep
		{
			get
			{
				WizardStep lastVisibleStep = LastStep;

				while (lastVisibleStep != null && !lastVisibleStep.Visible)
					lastVisibleStep = lastVisibleStep.previousStep;

				return lastVisibleStep;
			}
		}

		public virtual bool Visible
		{
			get
			{
				return true;
			}
		}

		public virtual bool SuppressDefaultNavigation
		{
			get
			{
				return false;
			}
		}

		public IEnumerable<WizardStep> RemainingSteps
		{
			get
			{
				WizardStep step = this.nextStep;

				while (step != null)
				{
					yield return step;
					step = step.nextStep;
				}
			}
		}
		#endregion

		#region Private / Protected
		private WizardStep previousStep, nextStep;

		/// <summary>
		/// Flag that is used to determine whether the UI should be preloaded.
		/// </summary>
		internal bool uiLoaded;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="WizardStep" /> class for derived types.
		/// </summary>
		protected WizardStep()
		{
		}
		#endregion

		#region Methods
		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand,
			Flags = System.Security.Permissions.SecurityPermissionFlag.Infrastructure)]
		public override object InitializeLifetimeService()
		{
			return null;
		}

		protected abstract object GetUserInterface(WizardController controller, bool preloading);

		protected abstract void WizardFinished(WizardController controller);

		protected abstract void WizardCanceled(WizardController controller);

		protected virtual bool CanFinishWizard()
		{
			return true;
		}

		/// <summary>
		/// Indicates whether the step allows the Wizard to be canceled.
		/// </summary>
		/// <remarks>
		/// <para>This method is used only when the user has not gone passed the step in the wizard.</para>
		/// <para>If the user has gone passed the step then the step will have no control over whether the wizard may be canceled.</para>
		/// <para>If the user navigates back to, or before, the step from a subsequent position in the wizard then the step may again control whether the wizard can be canceled.</para>
		/// <para>This behavior allows step creators to ensure that a user must supply information for a particular step before the wizard will finish or be canceled.  
		/// If the step allows the user to move to the next step then it's assumed that enough information has been gathered for the wizard to be finished or canceled, as far as the
		/// previous step is concerened.  Moving to a previous step or the same step will allow the step control over whether the wizard may be canceled again in case it must ask the user 
		/// to supply information again.</para>
		/// </remarks>
		/// <returns><b>true</b> if the step will allow the wizard to be canceled; otherwise, <b>false</b> to prevent the wizard from being canceled.</returns>
		protected virtual bool CanCancelWizard()
		{
			return true;
		}

		protected virtual bool CanNavigateTo(WizardController controller, WizardStep fromStep, bool forward)
		{
			return true;
		}

		protected virtual bool CanNavigateAway(WizardController controller, WizardStep toStep, bool forward)
		{
			return true;
		}

		protected virtual void NavigatedTo(WizardController controller, WizardStep fromStep, bool forward)
		{
		}

		protected virtual void NavigatedAway(WizardController controller, WizardStep toStep, bool forward)
		{
		}

		internal virtual bool CanFinishWizardInternal()
		{
			return CanFinishWizard();
		}

		internal bool CanCancelWizardInternal()
		{
			return CanCancelWizard();
		}

		internal object GetUserInterfaceInternal(WizardController controller, bool preloading)
		{
			uiLoaded = true;

			return GetUserInterface(controller, preloading);
		}

		internal void WizardFinishedInternal(WizardController controller)
		{
			WizardFinished(controller);
		}

		internal void WizardCanceledInternal(WizardController controller)
		{
			WizardCanceled(controller);
		}

		internal bool CanNavigateToInternal(WizardController controller, WizardStep fromStep, bool forward)
		{
			return CanNavigateTo(controller, fromStep, forward);
		}

		internal bool CanNavigateAwayInternal(WizardController controller, WizardStep toStep, bool forward)
		{
			return CanNavigateAway(controller, toStep, forward);
		}

		internal void NavigatedToInternal(WizardController controller, WizardStep fromStep, bool forward)
		{
			NavigatedTo(controller, fromStep, forward);
		}

		internal void NavigatedAwayInternal(WizardController controller, WizardStep toStep, bool forward)
		{
			NavigatedAway(controller, toStep, forward);
		}

		public TWizardStep InsertAfter<TWizardStep>(TWizardStep previous)
			where TWizardStep : WizardStep
		{
			if (previous == null)
				throw new ArgumentNullException("previous");

			if (previousStep == previous)
				return previous;

			if (previousStep != null)
				previousStep.nextStep = null;

			if (nextStep != null)
				nextStep.previousStep = null;

			previousStep = previous;
			nextStep = previousStep.nextStep;

			if (previousStep.nextStep != null)
				previousStep.nextStep.previousStep = this;

			previousStep.nextStep = this;

			return previous;
		}

		public TWizardStep InsertBefore<TWizardStep>(TWizardStep next)
			where TWizardStep : WizardStep
		{
			if (next == null)
				throw new ArgumentNullException("next");

			if (nextStep == next)
				return next;

			if (nextStep != null)
				nextStep.previousStep = null;

			if (previousStep != null)
				previousStep.nextStep = null;

			nextStep = next;
			previousStep = nextStep.previousStep;

			if (nextStep.previousStep != null)
				nextStep.previousStep.nextStep = this;

			nextStep.previousStep = this;

			return next;
		}

		public TWizardStep InjectAfter<TWizardStep>(TWizardStep previous)
			where TWizardStep : WizardStep
		{
			if (previous == null)
				throw new ArgumentNullException("previous");

			WizardStep firstStep = FirstStep;

			if (firstStep.previousStep == previous)
				return previous;

			firstStep.previousStep = previous;

			WizardStep lastStep = LastStep;

			lastStep.nextStep = previous.nextStep;

			if (previous.nextStep != null)
				previous.nextStep.previousStep = lastStep;

			previous.nextStep = firstStep;

			return previous;
		}

		public TWizardStep InjectBefore<TWizardStep>(TWizardStep next)
			where TWizardStep : WizardStep
		{
			if (next == null)
				throw new ArgumentNullException("next");

			WizardStep lastStep = LastStep;

			if (lastStep.nextStep == next)
				return next;

			lastStep.nextStep = next;

			WizardStep firstStep = FirstStep;

			firstStep.previousStep = next.previousStep;

			if (next.previousStep != null)
				next.previousStep.nextStep = firstStep;

			next.previousStep = lastStep;

			return next;
		}

		public TWizardStep SpliceAfter<TWizardStep>(TWizardStep previous)
			where TWizardStep : WizardStep
		{
			if (previous == null)
				throw new ArgumentNullException("previous");

			if (previousStep == previous)
				return previous;

			if (previousStep != null)
				previousStep.nextStep = null;

			previousStep = previous;

			WizardStep lastStep = LastStep;

			lastStep.nextStep = previousStep.nextStep;

			if (previousStep.nextStep != null)
				previousStep.nextStep.previousStep = lastStep;

			previousStep.nextStep = this;

			return previous;
		}

		public TWizardStep SpliceBefore<TWizardStep>(TWizardStep next)
			where TWizardStep : WizardStep
		{
			if (next == null)
				throw new ArgumentNullException("next");

			if (nextStep == next)
				return next;

			if (nextStep != null)
				nextStep.previousStep = null;

			nextStep = next;

			WizardStep firstStep = FirstStep;

			firstStep.previousStep = nextStep.previousStep;

			if (nextStep.previousStep != null)
				nextStep.previousStep.nextStep = firstStep;

			nextStep.previousStep = this;

			return next;
		}

		public void UnlinkFromNextStep()
		{
			nextStep.previousStep = null;
			nextStep = null;
		}

		public void UnlinkFromPreviousStep()
		{
			previousStep.nextStep = null;
			previousStep = null;
		}

		public void PreloadUserInterfacesFromHere(bool includeThisStep)
		{
			PreloadUserInterfacesFromHere(includeThisStep, null);
		}

		public void PreloadUserInterfacesFromHere(bool includeThisStep, Predicate<WizardStep> predicate)
		{
			foreach (WizardStep step in (includeThisStep) ? this : RemainingSteps)
				if (!step.uiLoaded && (predicate == null || predicate(step)))
					step.GetUserInterfaceInternal(null, true);
		}

		public IEnumerator<WizardStep> GetEnumerator(bool includeThisStep)
		{
			if (includeThisStep)
				yield return this;

			WizardStep step = this.nextStep;

			while (step != null)
			{
				yield return step;
				step = step.nextStep;
			}
		}

		public override string ToString()
		{
			return string.Format(System.Globalization.CultureInfo.CurrentCulture, WizardText.WizardStepToString, Name);
		}
		#endregion

		#region Events
		private readonly object StateChangedEventLock = new object();
		private EventHandler<WizardStepStateChangedEventArgs> StateChangedEvent;

		/// <summary>
		/// Event raised when the <see cref="CanNavigateTo" /> or <see cref="CanNavigateAway" /> methods will return different values for the same arguments.
		/// </summary>
		public event EventHandler<WizardStepStateChangedEventArgs> StateChanged
		{
			add
			{
				lock (StateChangedEventLock)
				{
					StateChangedEvent += value;
				}
			}
			remove
			{
				lock (StateChangedEventLock)
				{
					StateChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="StateChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="WizardStepStateChangedEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnStateChanged(WizardStepStateChangedEventArgs e)
		{
			EventHandler<WizardStepStateChangedEventArgs> handler = null;

			lock (StateChangedEventLock)
			{
				handler = StateChangedEvent;
			}

      if (handler != null)
			  handler(this, e);
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="WizardStep" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="WizardStep" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~WizardStep()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="WizardStep" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{

		}
		#endregion

		#region IEnumerable<WizardStep> Members
		public IEnumerator<WizardStep> GetEnumerator()
		{
			return GetEnumerator(true);
		}
		#endregion

		#region IEnumerable Members
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
		#endregion
	}
}
